package ripple.server.core.overlay.vector;

import java.util.ArrayList;
import java.util.List;

public class Coordinates {

    private int size;
    private int dimension;
    private int length;

    private int incompleteHyperplane;
    private int hyperplanarArea;

    public int getHyperplanarArea() {
        return this.hyperplanarArea;
    }

    public void setHyperplanarArea(int hyperplanarArea) {
        this.hyperplanarArea = hyperplanarArea;
    }

    public int getIncompleteHyperplane() {
        return this.incompleteHyperplane;
    }

    public void setIncompleteHyperplane(int incompleteHyperplane) {
        this.incompleteHyperplane = incompleteHyperplane;
    }

    public int getSize() {
        return this.size;
    }

    public void setSize(int size) {
        this.size = size;
    }

    public int getDimension() {
        return this.dimension;
    }

    public void setDimension(int dimension) {
        this.dimension = dimension;
    }

    public int getLength() {
        return this.length;
    }

    public void setLength(int length) {
        this.length = length;
    }

    public Coordinates(int size, int dimension) {
        this.setSize(size);
        this.setDimension(dimension);
        this.setLength((int) Math.ceil(Math.pow((double) size, 1 / (double) dimension)));

        int base = (int) Math.pow((double) this.getLength(), (double) (this.getDimension() - 1));
        if (this.getSize() % base == 0) {
            this.setIncompleteHyperplane(-1);
        } else {
            this.setIncompleteHyperplane((this.getSize() / base));
        }
        this.setHyperplanarArea(base);
    }

    private List<Integer> intToVector(int id) {
        List<Integer> list = new ArrayList<>();
        id--;
        for (int i = 0; i < this.dimension; i++) {
            list.add(id % this.length);
            id /= this.length;
        }
        return list;
    }

    private int intFromVector(List<Integer> vector) {
        int base = 1;
        int id = 1;

        for (int i = 0; i < this.dimension; i++) {
            id += vector.get(i) * base;
            base *= this.length;
        }
        return id;
    }

    public List<Integer> getList(int sourceId, int currentId) {
        if (sourceId > this.getSize() || currentId > this.getSize()) {
            return null;
        }

        List<Integer> sourceVector = this.intToVector(sourceId);
        List<Integer> currentVector = this.intToVector(currentId);

        // 已进行的推送轮次
        int round = this.getRound(sourceId, currentId);
        if (round >= this.getDimension()) {
            return new ArrayList<>();
        }

        List<Integer> list = new ArrayList<>();

        for (; round < this.getDimension(); round++) {
            // 已推送轮次 = 当前推送轮次-1 = 推送坐标序号
            list.addAll(this.getLine(currentId, round));
        }

        return list;
    }

    // 返回已完成的消息推送轮次：0~d
    public int getRound(int sourceId, int currentId) {

        if (sourceId == currentId) {
            return 0;
        }

        List<Integer> sourceVector = this.intToVector(sourceId);
        List<Integer> currentVector = this.intToVector(currentId);

        // 消息源节点在非满超平面上
        if (sourceVector.get(this.getDimension() - 1) == this.getIncompleteHyperplane()) {
            // 已是最后一轮推送
            if ((sourceId % this.getHyperplanarArea()) == (currentId % this.getHyperplanarArea())) {
                return this.getDimension();
            }

            // 将消息源节点等效为投影点
            sourceId = sourceId % this.getHyperplanarArea();
            sourceVector.set(this.getDimension() - 1, 0);
        }

        // 坐标值列表从0开始，因此-1
        int round = this.getDimension() - 1;
        while (round >= 0) {
            if (sourceVector.get(round) != currentVector.get(round)) {
                break;
            }
            round--;
        }
        round += 1;

        return round;
    }

    public List<Integer> getLine(int currentId, int linePosition) {
        // 需要进行节点等效处理的场景
        // 消息源节点处于非满超平面上，且当前节点为消息源节点，且不是进行最后一轮消息通信
        // 该轮消息推送中，消息源节点被等效为其投影点
        if (this.ifInIncompleteHyperplane(currentId) && linePosition != this.getDimension() - 1) {
            currentId = currentId % this.getHyperplanarArea();
            // System.out.println("[Coordinates] getLine()");
            // System.out.println("[Coordinates] getLine() " + "消息源节点处于非满超平面上");
        }

        List<Integer> lineList = new ArrayList<>();

        List<Integer> currentVector = this.intToVector(currentId);

        int lineBase = (int) Math.pow((double) this.getLength(), (double) linePosition);
        int idBase = currentId - (currentVector.get(linePosition) * lineBase);

        for (int i = 0; i < this.getLength(); i++) {
            int targetId = idBase + (i * lineBase);

            if (targetId > this.getSize()) {
                break;
            }

            if (targetId == currentId) {
                continue;
            }

            lineList.add(idBase + (i * lineBase));
        }

        return lineList;
    }

    public boolean ifInIncompleteHyperplane(int id) {
        return (id / this.getHyperplanarArea()) == this.getIncompleteHyperplane();
    }

}
